import _ from 'lodash';

export const enum EErrorType {
    AssetsUnrecognized = 'Assets Unrecognized',
    InvalidFileName = 'Invalid file name',
    CompressAtlasNonSquare = 'CompressAtlas non square',
    IncorrectSceneFile = 'Incorrect scene file',
    CompressTextureFailed = 'Compress texture failed',
    CompilationFailed = 'Compilation failed',
    NodeFailed = 'Node failed',
    CreateABFailed = 'CreateAB failed',
    AddABFailed = 'Add assetbuddle failed',
    Exception = 'Exception',
    Other = 'Other failed'
}

export interface IErrorFile {
    file: string,
    errorCode: string,
    errorMessage: string
}

export class LogParser {
    private readonly __excludes = [
        'Assertion failed on expression:',
        'GoogleApiClient$OnConnectionFailedListener.class',
        'Failed to unload ',
        'Licensing',
        'listen EADDRINUSE: address already in use',
        'Error cleaning up external process',
        'Error creating external process',
        'Licensing::Module',
        'LicensingClient',
        'Callback unregistration failed. Callback not registered.',
        'Reload of assemblies failed',
        'java.io.FileNotFoundException: https://dl.google.com/android/repository/sys-img/google-tv/sys-img2-1.xml',
        'Failed to connect to host: https://dl.google.com/android/repository/',
        'Failed to download any source lists',
        'connect to config.uca.cloud.unity3d.com',
        'PurchaseFailedEventArgs',
        'Failed to create Physics Mesh from source mesh',
        'Mounting archive file system for lighting data asset failed',
        'Error: The width or height of picture',
        'abort_threads: Failed aborting id:',
        'Errors during XML parse:',
        'Additionally, the fallback loader failed to parse the XML.',
        'Exception occured while accepting client connection: System.IO.IOException:',
        'Failed to send event androidAssetPacksBuild. Result: AnalyticsDisabled',
        '[W] FindFirstFile() failed:',
        'Curl error 23: Failed writing header',
        'Socket: bind failed, error: 以一种访问权限不允许的方式做了一个访问套接字的尝试。',
        'Cert verify failed. Certificate could not be verified (either omitted or unsupported). UnityTls error code: 7'
    ];

    private __error: string | null = null;
    get error(): string | null {
        return this.__error;
    }

    private __errorFiles: IErrorFile[] = [];
    get errorFiles() {
        return this.__errorFiles;
    }

    private __tag: EErrorType | null = null;
    public isKindOf(type: EErrorType): boolean {
        return this.__tag == type;
    }
    
    getUnrecognizedAssets(logstr: string) {
        let p = this._getUnrecognizedAssetsPatt()
        let unrecognizedAssets = logstr.match(p);
        return _.uniq(unrecognizedAssets);
    }
        
    private _getUnrecognizedAssetsPatt() {
        return /.*Unrecognized assets cannot be included in AssetBundles: "([^"]*)"/g;
    }
        
    private _printAssetsUnrecognized(logstr: string) {
        let p = this._getUnrecognizedAssetsPatt();
        this._printError(EErrorType.AssetsUnrecognized, logstr, p);
    }
    
    parseUnityBuildLog(logstr: string, exitCode: number) {
        logstr = logstr.split(/\r?\n+/).filter(v => !v.includes('[Licensing::')).join('\n');
        this._printInvalidFileName(logstr) || 
        this._printCompressNonSquare(logstr) || 
        this._printIncorrectSceneFile(logstr) || 
        this._printNotSupportedCompressed(logstr) || 
        this._printCompileFailed(logstr) || 
        this._printNodeFailed(logstr) || 
        this._printBuildABFailed(logstr) || 
        this._printAddAssetBuddleFailed(logstr) ||         
        // dynamicRemoves动态删除某些库后，unity会抛出一些错误，故只检查exitCode不为0的情况
        // 比如 System.IO.FileNotFoundException: Could not find file 'F:\games\fysw_webgl_develop\project\Library\ScriptAssemblies\AVProVideo.Demos.dll'.
        // ref: http://builder.fygame.com:8282/hudson/view/%E4%B8%89%E5%91%B3%E4%B9%A6%E5%B1%8B/job/%E4%B8%89%E5%91%B3%E4%B9%A6%E5%B1%8B-%E5%86%85%E7%BD%91/623/console
        exitCode != 0 && this._printException(logstr) || 
        this._printOtherFailed(logstr) || 
        this._printAssetsUnrecognized(logstr);
    }

    parseTscLog(logstr: string) {
        this._printCompileFailed(logstr);
    }
    
    private _printInvalidFileName(logstr: string): string[] | null {
        let p = /failed: 文件名不合法/g;
        return this._printError(EErrorType.InvalidFileName, logstr, p);
    }
        
    private _printCompressNonSquare(logstr: string): string[] | null {
        let p = /Updating ([\w:\\\/\.]+) - GUID: .*\nCan not compress a non-square texture with Sprites to PVRTC format./g;
        return this._printError(EErrorType.CompressAtlasNonSquare, logstr, p);
    }
        
    private _printIncorrectSceneFile(logstr: string): string[] | null {
        let p = /\'[\w:\\\/\.]+\'\s+is an incorrect path for a scene file. BuildPlayer expects paths relative to the project folder.$/g;
        return this._printError(EErrorType.IncorrectSceneFile, logstr, p);
    }
    
    private _printNotSupportedCompressed(logstr: string): string[] | null {
        let p = /\w+ compressed textures are not supported when publishing to \w+/g;
        return this._printError(EErrorType.CompressTextureFailed, logstr, p);
    }
    
    private _printCompileFailed(logstr: string): string[] | null {
        const p = /([\w:\\\/\.]+)\(\d+,\d+\):\s+error (CS\d+|TS\d+):.*/g;
        const mchs = this._printError(EErrorType.CompilationFailed, logstr, p);
        if (mchs) {
            for (const s of mchs) {
                const rst = s.match(/([\w:\\\/\.]+)\(\d+,\d+\):\s+error (CS\d+|TS\d+):\s*(.*)/);
                if (rst) {
                    this.__errorFiles.push({ file: rst[1], errorCode: rst[2], errorMessage: rst[3] });
                }
            }
        }
        return mchs;
    }
    
    private _printNodeFailed(logstr: string): string[] | null {
        let p = /\b(?:Error|TypeError)\b: .*/g;
        return this._printError(EErrorType.NodeFailed, logstr, p);
    }
    
    private _printBuildABFailed(logstr: string): string[] | null {
        let p = /Crash!!!/g;
        return this._printError(EErrorType.CreateABFailed, logstr, p);
    }
    
    private _printAddAssetBuddleFailed(logstr: string): string[] | null {
        let p = /.*error: Invalid filename\.  Unable to add/g;
        return this._printError(EErrorType.AddABFailed, logstr, p);
    }
    
    private _printException(logstr: string): string[] | null {
        let p = /.*Exception\:.*/g;
        return this._printError(EErrorType.Exception, logstr, p);
    }
    
    private _printOtherFailed(logstr: string): string[] | null {
        let p = /.*\b[Ff]ailed\b.*|.*Fatal [Ee]rror.*/g
        return this._printError(EErrorType.Other, logstr, p);
    }

    private _printError(tag: EErrorType, logstr: string, p: RegExp, filter?: (value: string, index: number, array: string[]) => boolean): string[] | null {
        const mchs = logstr.match(p);
        if(!mchs) return null;

        let arr = _.uniq(mchs);
        this._exclude_log(arr);
        if (filter != null) {
            arr = arr.filter(filter);
        }
        if (arr.length == 0) return null;
    
        this.__tag = tag;
        this.__error = arr.join('\n');
        console.error (`--${tag}! ${arr.length} error(s):`);
        for(let a of arr)
            console.error(a);
        return arr;
    }
    
    private _exclude_log(arr: string[]) {
        for(let i = arr.length - 1; i >= 0; i--) {
            for(let exclude of this.__excludes) {
                if(arr[i].indexOf(exclude) >= 0) {
                    arr.splice(i, 1);
                    break;
                }
            }
        }
    }
}